/**
 * TLSConnectionState
 *
 * This class encapsulates the read or write state of a TLS connection,
 * and implementes the encrypting and hashing of packets.
 * Copyright (c) 2007 Henri Torgemane
 *
 * See LICENSE.txt for full license information.
 */
package com.hurlant.crypto.tls
{
	import flash.utils.IDataInput;
	import flash.utils.ByteArray;
	import com.hurlant.crypto.hash.MD5;
	import com.hurlant.crypto.hash.HMAC;
	import com.hurlant.crypto.hash.IHash;
	import com.hurlant.crypto.symmetric.ICipher;
	import com.hurlant.crypto.symmetric.IVMode;
	import com.hurlant.util.Hex;
	import com.hurlant.util.ArrayUtil;

	public class TLSConnectionState
	{

		// compression state

		// cipher state
		private var bulkCipher:uint;
		private var cipherType:uint;
		private var CIPHER_key:ByteArray;
		private var CIPHER_IV:ByteArray;
		private var cipher:ICipher;
		private var ivmode:IVMode;

		// mac secret
		private var macAlgorithm:uint;
		private var MAC_write_secret:ByteArray;
		private var hmac:HMAC;

		// sequence number. uint64
		private var seq_lo:uint;
		private var seq_hi:uint;


		public function TLSConnectionState(bulkCipher:uint=0, cipherType:uint=0, macAlgorithm:uint=0, mac:ByteArray=null, key:ByteArray=null, IV:ByteArray=null)
		{
			this.bulkCipher=bulkCipher;
			this.cipherType=cipherType;
			this.macAlgorithm=macAlgorithm;
			MAC_write_secret=mac;
			hmac=MACs.getHMAC(macAlgorithm);
			CIPHER_key=key;
			CIPHER_IV=IV;
			cipher=BulkCiphers.getCipher(bulkCipher, key);
			if (cipher is IVMode)
			{
				ivmode=cipher as IVMode;
				ivmode.IV=IV;
			}
		}

		public function decrypt(type:uint, length:uint, p:ByteArray):ByteArray
		{
			// decompression is a nop.

			if (cipherType == BulkCiphers.STREAM_CIPHER)
			{
				if (bulkCipher == BulkCiphers.NULL)
				{
					// no-op
				}
				else
				{
					cipher.decrypt(p);
				}
			}
			else
			{
				// block cipher
				var nextIV:ByteArray=new ByteArray;
				nextIV.writeBytes(p, p.length - CIPHER_IV.length, CIPHER_IV.length);

				cipher.decrypt(p);

				CIPHER_IV=nextIV;
				ivmode.IV=nextIV;
			}
			if (macAlgorithm != MACs.NULL)
			{
				var data:ByteArray=new ByteArray;
				var len:uint=p.length - hmac.getHashSize();
				data.writeUnsignedInt(seq_hi);
				data.writeUnsignedInt(seq_lo);
				data.writeByte(type);
				data.writeShort(TLSEngine.TLS_VERSION);
				data.writeShort(len);
				if (len != 0)
				{
					data.writeBytes(p, 0, len);
				}
				var mac:ByteArray=hmac.compute(MAC_write_secret, data);
				// compare "mac" with the last X bytes of p.
				var mac_received:ByteArray=new ByteArray;
				mac_received.writeBytes(p, len, hmac.getHashSize());
				if (ArrayUtil.equals(mac, mac_received))
				{
					// happy happy joy joy
				}
				else
				{
					throw new TLSError("Bad Mac Data", TLSError.bad_record_mac);
				}
				p.length=len;
				p.position=0;
			}
			// increment seq
			seq_lo++;
			if (seq_lo == 0)
				seq_hi++;
			return p;
		}

		public function encrypt(type:uint, p:ByteArray):ByteArray
		{
			var mac:ByteArray=null;
			if (macAlgorithm != MACs.NULL)
			{
				var data:ByteArray=new ByteArray;
				data.writeUnsignedInt(seq_hi);
				data.writeUnsignedInt(seq_lo);
				data.writeByte(type);
				data.writeShort(TLSEngine.TLS_VERSION);
				data.writeShort(p.length);
				if (p.length != 0)
				{
					data.writeBytes(p, 0, p.length);
				}
				mac=hmac.compute(MAC_write_secret, data);
				p.position=p.length;
				p.writeBytes(mac);
			}
			p.position=0;
			if (cipherType == BulkCiphers.STREAM_CIPHER)
			{
				// stream cipher
				if (bulkCipher == BulkCiphers.NULL)
				{
					// no-op
				}
				else
				{
					cipher.encrypt(p);
				}
			}
			else
			{
				// block cipher
				cipher.encrypt(p);
				// adjust IV
				var nextIV:ByteArray=new ByteArray;
				nextIV.writeBytes(p, p.length - CIPHER_IV.length, CIPHER_IV.length);
				CIPHER_IV=nextIV;
				ivmode.IV=nextIV;
			}
			// increment seq
			seq_lo++;
			if (seq_lo == 0)
				seq_hi++;
			// compression is a nop.
			return p;
		}

	}
}

